完成评论详情布局
概述
本节完成评论详情页的三栏布局实现:左侧快捷操作栏、中间评论列表、右侧信息栏(用户信息+关联主题)。通过横向 Flex 布局确定各区域的空间比例,并使用卡片阴影增强视觉层次感。
页面结构
三栏布局示意
┌──────────┬─────────────────────────┬──────────────┐
│ 快捷操作 │ 评论列表 │ 信息栏 │
│ (固定宽度) │ (自适应,最小宽度) │ (固定宽度) │
│ │ │ │
│ [回复] │ ┌───────────────────┐ │ 用户信息 │
│ [点赞] │ │ 评论 1 │ │ │
│ [收藏] │ │ 内容... │ │ ────────── │
│ [分享] │ └───────────────────┘ │ │
│ │ ┌───────────────────┐ │ 关联主题 │
│ │ │ 评论 2 │ │ - 主题A │
│ │ │ 内容... │ │ - 主题B │
│ │ └───────────────────┘ │ - 主题C │
└──────────┴─────────────────────────┴──────────────┘
text
组件目录结构
components/comments/
├── CommentsControl.vue # 左侧快捷操作栏
├── CommentItem.vue # 单条评论组件
└── CommentsInfo.vue # 右侧信息栏
text
组件实现
评论详情页主布局
<!-- pages/CommentsDetail.vue -->
<script setup lang="ts">
import CommentsControl from '@/components/comments/CommentsControl.vue'
import CommentItem from '@/components/comments/CommentItem.vue'
import CommentsInfo from '@/components/comments/CommentsInfo.vue'
const comments = [
{ id: 1, author: '张三', content: '非常好的课程,学到了很多', time: '2026-05-18' },
{ id: 2, author: '李四', content: '实战项目很有帮助', time: '2026-05-17' },
{ id: 3, author: '王五', content: '期待更多进阶内容', time: '2026-05-16' }
]
</script>
<template>
<div class="comments-detail">
<!-- 左侧:快捷操作 -->
<aside class="comments-aside">
<CommentsControl />
</aside>
<!-- 中间:评论列表(自适应宽度) -->
<main class="comments-main">
<CommentItem
v-for="comment in comments"
:key="comment.id"
:comment="comment"
/>
</main>
<!-- 右侧:信息栏 -->
<aside class="comments-sidebar">
<CommentsInfo />
</aside>
</div>
</template>
<style scoped>
.comments-detail {
display: flex;
gap: 16px;
height: 100%;
padding: 16px;
}
.comments-aside {
flex-shrink: 0;
width: 48px;
}
.comments-main {
flex: 1;
min-width: 400px;
overflow-y: auto;
}
.comments-sidebar {
flex-shrink: 0;
width: 240px;
}
</style>
vue
快捷操作栏组件
<!-- components/comments/CommentsControl.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const actions = [
{ icon: 'Reply', label: '回复', active: false },
{ icon: 'Star', label: '点赞', active: false },
{ icon: 'Bookmark', label: '收藏', active: false },
{ icon: 'Share', label: '分享', active: false }
] as const
type ActionKey = typeof actions[number]['icon']
const activeActions = ref<Set<ActionKey>>(new Set())
function toggleAction(icon: ActionKey): void {
if (activeActions.value.has(icon)) {
activeActions.value.delete(icon)
} else {
activeActions.value.add(icon)
}
}
</script>
<template>
<div class="comments-control">
<el-tooltip
v-for="action in actions"
:key="action.icon"
:content="action.label"
placement="left"
>
<el-button
:icon="action.icon"
circle
:type="activeActions.has(action.icon) ? 'primary' : 'default'"
size="small"
@click="toggleAction(action.icon)"
/>
</el-tooltip>
</div>
</template>
<style scoped>
.comments-control {
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
padding-top: 8px;
}
</style>
vue
评论项组件
<!-- components/comments/CommentItem.vue -->
<script setup lang="ts">
interface Comment {
id: number
author: string
content: string
time: string
}
defineProps<{
comment: Comment
}>()
</script>
<template>
<el-card shadow="hover" class="comment-card">
<div class="comment-header">
<el-avatar :size="36">{{ comment.author[0] }}</el-avatar>
<div class="comment-meta">
<span class="comment-author">{{ comment.author }}</span>
<span class="comment-time">{{ comment.time }}</span>
</div>
</div>
<div class="comment-body">{{ comment.content }}</div>
<div class="comment-footer">
<el-button text size="small">回复</el-button>
<el-button text size="small">点赞</el-button>
</div>
</el-card>
</template>
<style scoped>
.comment-card {
margin-bottom: 12px;
}
.comment-header {
display: flex;
align-items: center;
gap: 8px;
}
.comment-meta {
display: flex;
flex-direction: column;
}
.comment-author {
font-weight: 500;
font-size: 14px;
}
.comment-time {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.comment-body {
margin: 12px 0;
line-height: 1.6;
font-size: 14px;
}
.comment-footer {
display: flex;
gap: 4px;
}
</style>
vue
右侧信息栏组件
<!-- components/comments/CommentsInfo.vue -->
<script setup lang="ts">
const relatedTopics = [
{ id: 1, title: 'Vue 3 Composition API 实战', replies: 23 },
{ id: 2, title: 'TypeScript 进阶技巧', replies: 15 },
{ id: 3, title: '前端性能优化实践', replies: 31 }
]
const userInfo = {
name: '课程讲师',
avatar: '',
bio: '全栈开发工程师,10年前端经验',
courses: 12
}
</script>
<template>
<div class="comments-info">
<!-- 用户信息卡片 -->
<el-card shadow="never" class="info-card">
<template #header>评论者信息</template>
<div class="user-info">
<el-avatar :size="40">{{ userInfo.name[0] }}</el-avatar>
<div class="user-detail">
<span class="user-name">{{ userInfo.name }}</span>
<span class="user-bio">{{ userInfo.bio }}</span>
</div>
</div>
</el-card>
<!-- 关联主题 -->
<el-card shadow="never" class="info-card">
<template #header>关联主题</template>
<div class="topic-list">
<div v-for="topic in relatedTopics" :key="topic.id" class="topic-item">
<span class="topic-title">{{ topic.title }}</span>
<span class="topic-replies">{{ topic.replies }} 回复</span>
</div>
</div>
</el-card>
</div>
</template>
<style scoped>
.comments-info {
display: flex;
flex-direction: column;
gap: 12px;
}
.user-info {
display: flex;
gap: 8px;
align-items: flex-start;
}
.user-detail {
display: flex;
flex-direction: column;
}
.user-name {
font-weight: 500;
}
.user-bio {
font-size: 12px;
color: var(--el-text-color-secondary);
margin-top: 4px;
}
.topic-item {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid var(--el-border-color-lighter);
}
.topic-item:last-child {
border-bottom: none;
}
.topic-title {
font-size: 13px;
flex: 1;
margin-right: 8px;
}
.topic-replies {
font-size: 12px;
color: var(--el-text-color-secondary);
white-space: nowrap;
}
</style>
vue
布局关键样式
| 区域 | Flex 属性 | 宽度策略 |
|---|---|---|
| 左侧操作栏 | flex-shrink: 0 | 固定 48px |
| 中间评论列表 | flex: 1 | 自适应,min-width: 400px |
| 右侧信息栏 | flex-shrink: 0 | 固定 240px |
实践要点
- 三栏布局使用
display: flex+gap: 16px实现间距,避免使用 margin - 中间评论列表区域设置
min-width保证最小可用宽度,overflow-y: auto处理溢出滚动 - 评论卡片之间使用
shadow="hover"悬浮阴影,增强交互视觉反馈 - 快捷操作栏使用
el-tooltip显示按钮用途,保持界面简洁 - 右侧信息栏分为用户信息和关联主题两个独立卡片,结构清晰
↑